package com.raymond.cmpp.common.utils;



import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * CMPP消息id算法
 *
 * 信息标识，生成算法如下：
 * 采用64位（8字节）的整数：
 * （1）时间（格式为MMddHHmmss，即月日时分秒）：bit64~bit39，其中
 * bit64~bit61：月份的二进制表示；
 * bit60~bit56：日的二进制表示；
 * bit55~bit51：小时的二进制表示；
 * bit50~bit45：分的二进制表示；
 * bit44~bit39：秒的二进制表示；
 * （2）短信网关代码：bit38~bit17，把短信网关的代码转换为整数填写到该字段中；
 * （3）序列号：bit16~bit1，顺序增加，步长为1，循环使用。
 * 各部分如不能填满，左补零，右对齐。
 * （SP根据请求和应答消息的Sequence_Id一致性就可得到CMPP_Submit消息的Msg_Id）
 *
 * @author :  raymond
 * @version :  V1.0
 * @date :  2021-04-28 11:39
 */
public class CmppMsgId {

    private volatile long time;

    private long lastTime;

    private long gatewayCode;

    private long sequence;

    /**5位的工作机器id**/
    private int gatewayCodeBits = 12;
    /**每毫秒内产生的id数 2 的 12次方**/
    private int sequenceBits = 16;

    /**序号最大值**/
    private int maxSequence = ~(-1 << sequenceBits);
    /**这个是二进制运算，就是5 bit最多只能有31个数字，也就是说工作机器id最多只能是32以内**/
    @SuppressWarnings("FieldCanBeLocal")
    private int maxGatewayCode = ~(-1 << gatewayCodeBits);

    /**工作机器id需要左移位数**/
    private int gatewayCodeShift = sequenceBits;

    private static CmppMsgId cmppMsgId = null;

    private static CmppMsgId getInstance(int gatewayCode) {
        if (cmppMsgId == null) {
            synchronized (CmppMsgId.class) {
                if (cmppMsgId == null) {
                    cmppMsgId = new CmppMsgId(gatewayCode);
                }
            }
        }
        return cmppMsgId;
    }

    public synchronized long nextId() {
        long now = this.time;
        if (now == this.lastTime) {
            if (++sequence > maxSequence) {
                now = tilNextMillis();
                sequence = 0;
            }
        } else {
            sequence = 0;
        }
        this.lastTime = now;
        return now | (gatewayCode << gatewayCodeShift) | sequence;
    }
    @SuppressWarnings("all")
    private long tilNextMillis() {
        long timestamp = this.time;
        while (timestamp <= this.lastTime) {
            timestamp = this.time;
        }
        return timestamp;
    }

    private CmppMsgId(int gatewayCode) {
        // 检查机房id和工作机器id是否超过31 不能小于0
        if (gatewayCode > maxGatewayCode || maxGatewayCode < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0", maxGatewayCode));
        }
        this.gatewayCode = gatewayCode;
        ThreadPoolUtil.getScheduledThreadExecutor(1, "CMPP消息ID")
                .scheduleWithFixedDelay(() -> {
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTimeInMillis(System.currentTimeMillis());
                    long month = calendar.get(Calendar.MONTH) + 1;
                    long day = calendar.get(Calendar.DAY_OF_MONTH);
                    long hour = calendar.get(Calendar.HOUR_OF_DAY);
                    long min = calendar.get(Calendar.MINUTE);
                    long sec = calendar.get(Calendar.SECOND);
                    time = (month << 60) | (day << 55) | (hour << 50) | (min << 44) | (sec << 38);
                }, 0, 1, TimeUnit.SECONDS);
    }

    public static void main(String[] args) throws InterruptedException {
        CmppMsgId cmppMsgId = CmppMsgId.getInstance(1);
        Thread.sleep(1000);
        int count = 1 << 17;
        List<Long> list = new ArrayList<>();
        long start = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            list.add(cmppMsgId.nextId());
        }
        System.out.println("耗时:" + (System.currentTimeMillis() - start));
        System.out.println(list.size());
        Set<Long> set = new HashSet<>(list);
        System.out.println(set.size());
    }
}
